package cn.zxone.feishu.demo.common.config;

import cn.zxone.feishu.demo.common.handler.EventCallbackHandler;
import cn.zxone.feishu.demo.common.properties.FeiShuProperties;
import com.lark.oapi.Client;
import com.lark.oapi.core.cache.ICache;
import com.lark.oapi.core.enums.BaseUrlEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *     飞书 openApi 客户端管理
 * </p>
 *
 * @author zhangxue
 * @since 2025/4/9 11:39
 */
@Slf4j
public class FeiShuClientManager {


    private final FeiShuProperties feiShuProperties;
    private final StringRedisTemplate stringRedisTemplate;

    public FeiShuProperties getProperties() {
        return this.feiShuProperties;
    }

    // 应用别名与应用ID缓存
    private static final Map<String, String> ALIAS_APP_ID = new ConcurrentHashMap<>(64);

    // 应用ID与Client缓存
    private static final Map<String, Client> CLIENT_CACHE_BY_APP_ID = new ConcurrentHashMap<>(64);


    public FeiShuClientManager(FeiShuProperties feiShuProperties, StringRedisTemplate stringRedisTemplate) {

        log.debug(">>>>>>>> 初始化飞书配置 开始 ...");

        this.feiShuProperties = feiShuProperties;
        this.stringRedisTemplate = stringRedisTemplate;

        Map<String, FeiShuProperties.AppConfig> appConfigs = feiShuProperties.getAppConfigs();

        Set<Map.Entry<String, FeiShuProperties.AppConfig>> entries = appConfigs.entrySet();

        for (Map.Entry<String, FeiShuProperties.AppConfig> entry : entries) {

            String alias = entry.getKey().toLowerCase();

            FeiShuProperties.AppConfig appConfig = entry.getValue();

            appConfig.setAppAlias(alias);

            String appId = appConfig.getAppId();

            log.debug(">>>>>>>> 飞书应用初始化 alias: {}, appId: {}", alias, appId);

            // 构建client
            Client client = this.buildClient(feiShuProperties, appConfig);

            // 启动事件回调处理
            EventCallbackHandler.buildAndStart(appConfig);

            ALIAS_APP_ID.put(alias, appId);
            CLIENT_CACHE_BY_APP_ID.put(appId, client);
        }

        log.debug(">>>>>>>> 初始化飞书配置 结束 ...");
    }


    /**
     * <p>
     * 通过应用别名获取client
     * </p>
     *
     * @param appAlias 应用别名
     * @return {@link Client }
     */
    public Client getClientByAlias(String appAlias) {

        String appId = ALIAS_APP_ID.get(appAlias.toLowerCase());

        if (appId == null) {
            return null;
        }
        return CLIENT_CACHE_BY_APP_ID.get(appId);
    }

    /**
     * <p>
     * 通过应用ID获取client
     * </p>
     *
     * @param appId 应用ID
     * @return {@link Client }
     */
    public Client getClientById(String appId) {

        return CLIENT_CACHE_BY_APP_ID.get(appId);
    }

    /**
     * <p>
     * 构建client
     * </p>
     *
     * @param properties 配置
     * @param appConfig  应用配置
     * @return {@link Client }
     */
    private Client buildClient(FeiShuProperties properties, FeiShuProperties.AppConfig appConfig) {

        // 构建client
        Client.Builder builder = Client.newBuilder(appConfig.getAppId(), appConfig.getAppSecret());

        // 设置域名，默认为飞书
        builder.openBaseUrl(BaseUrlEnum.FeiShu);

        // 设置 App 类型为商店应用。如果你是 ISV 开发者，则必须设置该选项。关于商店应用的开发指南，可参见 ISV（商店应用）开发指南-oapi-sdk-java
        if (appConfig.isMarketplaceApp()) {

            builder.marketplaceApp();
        }

        // 服务台的 ID 和 token。仅在调用服务台业务的 API 时需要配置。可在服务台管理后台设置中心 > API 凭证 处获取，详情参见 服务台接入指南
        // 注意：服务台的 ID、Token 只有服务台创建者可以查看到。
        if (StringUtils.isNotBlank(appConfig.getHelpDeskId()) && StringUtils.isNotBlank(appConfig.getHelpDeskToken())) {

            builder.helpDeskCredential(appConfig.getHelpDeskId(), appConfig.getHelpDeskToken());
        }

        // 设置 SDK 内置的 Http Client 的请求超时时间。默认为 0 表示永不超时
        builder.requestTimeout(properties.getRequestTimeout(), TimeUnit.SECONDS);

        // 设置是否开启 HTTP 请求参数和响应参数的日志打印开关。开启后，在 debug 模式下会打印 HTTP 请求和响应的 headers、body 等信息。在排查问题时开启该选项，有利于问题的排查
        builder.logReqAtDebug(properties.isLogReqAtDebug());

        // 设置是否开启 TenantAccessToken （应用访问凭证）的自动获取与缓存。
        // 若配置该选项，表示关闭自动获取与缓存 TenantAccessToken；若不配置则为开启。
        if (properties.isDisableTokenCache()) {

            builder.disableTokenCache();
        }

        // 设置 Token 缓存器，用于缓存 Token 和 appTIcket，默认实现为内存
        if (("redis").equalsIgnoreCase(properties.getTokenCache())) {

            builder.tokenCache(this.buildCache(appConfig));
        }

        return builder.build();
    }

    /**
     * <p>
     * 构建缓存器
     * </p>
     *
     * @return {@link ICache }
     */
    private ICache buildCache(FeiShuProperties.AppConfig appConfig) {

        log.debug(">>>>>>>> 构建飞书应用Redis缓存器 alias: {}, appId: {}", appConfig.getAppAlias(), appConfig.getAppId());

        return new ICache() {
            @Override
            public String get(String key) {

                return stringRedisTemplate.opsForValue().get("feishu:" + key);
            }

            @Override
            public void set(String key, String value, int expire, TimeUnit timeUnit) {

                stringRedisTemplate.opsForValue().set("feishu:" + key, value, (long) expire, timeUnit);
            }
        };
    }

}
